#include "CurrentContext.h"

using namespace DirectX;


CurrentContext::CurrentContext()
{
}

CurrentContext& CurrentContext::GetInstance()
{
  static CurrentContext instance;
  
  return instance;
}

bool CurrentContext::Init(HINSTANCE hInstance, HWND window)
{
  RECT dimensions;
  GetClientRect(window, &dimensions);

  unsigned int width = dimensions.right - dimensions.left;
  unsigned int height = dimensions.bottom - dimensions.top;

  Width = width;
  Height = height;

  D3D_DRIVER_TYPE driverTypes[] = 
  { 
    D3D_DRIVER_TYPE_HARDWARE, 
    D3D_DRIVER_TYPE_WARP, 
    D3D_DRIVER_TYPE_SOFTWARE, 
    D3D_DRIVER_TYPE_REFERENCE
  };

  unsigned int totalDriverTypes = ARRAYSIZE(driverTypes);

  D3D_FEATURE_LEVEL featureLevels[] = 
  { 
    D3D_FEATURE_LEVEL_11_0, 
    D3D_FEATURE_LEVEL_10_1, 
    D3D_FEATURE_LEVEL_10_0 
  };

  unsigned int totalFeatureLevels = ARRAYSIZE(featureLevels);

  DXGI_SWAP_CHAIN_DESC swapChainDesc;
  ZeroMemory(&swapChainDesc, sizeof(swapChainDesc));
  swapChainDesc.BufferCount = 1;
  swapChainDesc.BufferDesc.Width = width;
  swapChainDesc.BufferDesc.Height = height;
  swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
  swapChainDesc.BufferDesc.RefreshRate.Numerator = 60;
  swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
  swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
  swapChainDesc.Windowed = true;
  swapChainDesc.SampleDesc.Count = 1;
  swapChainDesc.SampleDesc.Quality = 0;
  swapChainDesc.OutputWindow = window;
  swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
  swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
  swapChainDesc.Flags = 0;
  swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

  unsigned int creationFlags = 0;
#ifdef _DEBUG
  creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

  HRESULT result;

  for(unsigned int driver = 0; driver < totalDriverTypes; ++driver)
  {
    result = D3D11CreateDeviceAndSwapChain(0, driverTypes[driver], 0, creationFlags, featureLevels, totalFeatureLevels,
      D3D11_SDK_VERSION, &swapChainDesc, &SwapChain, &Device, &FeatureLevel, &DeviceContext);

    if(SUCCEEDED(result))
    {
      DriverType = driverTypes[driver];
      break;
    }
  }

  if(FAILED(result))
  {
    DXTRACE_MSG("Failed to create the Direct3D device!");
    return false;
  }

  ID3D11RasterizerState* rasterState;
  D3D11_RASTERIZER_DESC rasterDesc;
  ZeroMemory(&rasterDesc, sizeof(rasterDesc));

  rasterDesc.AntialiasedLineEnable = false;
  rasterDesc.CullMode = D3D11_CULL_FRONT;
	rasterDesc.DepthBias = 0;
	rasterDesc.DepthBiasClamp = 0.0f;
	rasterDesc.DepthClipEnable = true;
	rasterDesc.FillMode = D3D11_FILL_SOLID;
	rasterDesc.FrontCounterClockwise = true;
	rasterDesc.MultisampleEnable = false;
	rasterDesc.ScissorEnable = false;
	rasterDesc.SlopeScaledDepthBias = 0.0f;
  rasterDesc.AntialiasedLineEnable = true;
  
  
  
  result =  Device->CreateRasterizerState(&rasterDesc, &rasterState);
  if(FAILED(result))
  {
    DXTRACE_MSG("Failed to create render state.");
    return false;
  }

  DeviceContext->RSSetState(rasterState);

  ID3D11Texture2D* backBufferTexture;
  
  result = SwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*) &backBufferTexture);

  if(FAILED(result))
  {
    DXTRACE_MSG("Failed to get the swap chain back buffer!");
    return false;
  }

  result = Device->CreateRenderTargetView(backBufferTexture, 0, &BackBufferTarget);

  if(backBufferTexture)
    backBufferTexture->Release();

  if(FAILED(result))
  {
    DXTRACE_MSG("Failed to create the render window!");
    return false;
  }

  D3D11_TEXTURE2D_DESC depthTexDesc;
  ZeroMemory( &depthTexDesc, sizeof( depthTexDesc ) );
  depthTexDesc.Width = width;
  depthTexDesc.Height = height;
  depthTexDesc.MipLevels = 1;
  depthTexDesc.ArraySize = 1;
  depthTexDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
  depthTexDesc.SampleDesc.Count = 1;
  depthTexDesc.SampleDesc.Quality = 0;
  depthTexDesc.Usage = D3D11_USAGE_DEFAULT;
  depthTexDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
  depthTexDesc.CPUAccessFlags = 0;
  depthTexDesc.MiscFlags = 0;
  

  result = Device->CreateTexture2D( &depthTexDesc, NULL, &DepthTexture );

  if( FAILED( result ) )
  {
    DXTRACE_MSG( "Failed to create the depth texture!" );
    return false;
  }

  D3D11_DEPTH_STENCIL_DESC descDS;
  ZeroMemory(&descDS, sizeof(descDS));

  
  descDS.DepthEnable = true;
  descDS.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
  descDS.DepthFunc = D3D11_COMPARISON_LESS;
  descDS.StencilEnable = true;
  descDS.StencilReadMask = 0xff;
  descDS.StencilWriteMask = 0xff;

  descDS.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
  descDS.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR;
  descDS.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
  descDS.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;

  // Stencil operations if pixel is back-facing.
  descDS.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
  descDS.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR;
  descDS.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
  descDS.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS;

  result = Device->CreateDepthStencilState(&descDS, &DepthStencilState);
  if(FAILED(result))
  {
    DXTRACE_MSG("Failed to create depth stencil view!");
    return false;
  }

  DeviceContext->OMSetDepthStencilState(DepthStencilState, 1);

  // Create the depth stencil view
  D3D11_DEPTH_STENCIL_VIEW_DESC descDSV;
  ZeroMemory( &descDSV, sizeof( descDSV ) );
  
  descDSV.Format = depthTexDesc.Format;
  descDSV.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
  descDSV.Texture2D.MipSlice = 0;

  result = this->Device->CreateDepthStencilView( DepthTexture, &descDSV, &DepthStencilView );

  if( FAILED( result ) )
  {
    DXTRACE_MSG( "Failed to create the depth stencil target view!" );
    return false;
  }

  DeviceContext->OMSetRenderTargets(1, &BackBufferTarget, DepthStencilView);


  D3D11_VIEWPORT viewport;
  viewport.Width = static_cast<float>(width);
  viewport.Height = static_cast<float>(height);
  viewport.MinDepth = 0.0f;
  viewport.MaxDepth = 1.0f;
  viewport.TopLeftX = 0.0f;
  viewport.TopLeftY = 0.0f;

  DeviceContext->RSSetViewports(1, &viewport);

  ZeroMemory( KeyboardKeys, sizeof( KeyboardKeys ) );
  ZeroMemory( PrevKeyboardKeys, sizeof( PrevKeyboardKeys ) );

  result = DirectInput8Create( hInstance, DIRECTINPUT_VERSION, IID_IDirectInput8, ( void** )&DirectInput, 0 );

  if( FAILED( result ) )
  { 
    return false;
  }

  result = DirectInput->CreateDevice( GUID_SysKeyboard, &KeyboardDevice, 0 );

  if( FAILED( result ) )
  { 
    return false;
  }

  result = KeyboardDevice->SetDataFormat( &c_dfDIKeyboard );

  if( FAILED( result ) )
  { 
    return false;
  }

  result = KeyboardDevice->SetCooperativeLevel( window, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE );

  if( FAILED( result ) )
  { 
    return false;
  }

  result = KeyboardDevice->Acquire( );

  if( FAILED( result ) )
  { 
    return false;
  }

  MousePosX = MousePosY = MouseWheel = 0;

  result = DirectInput->CreateDevice( GUID_SysMouse, &MouseDevice, 0 );

  if( FAILED( result ) )
  { 
    return false;
  }

  result = MouseDevice->SetDataFormat( &c_dfDIMouse );

  if( FAILED( result ) )
  { 
    return false;
  }

  result = MouseDevice->SetCooperativeLevel( window, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE );

  if( FAILED( result ) )
  { 
    return false;
  }

  result = MouseDevice->Acquire( );

  if( FAILED( result ) )
  { 
    return false;
  }


  ID3DBlob* buffer = 0;
  bool compileResult = CompileShader("Shaders/Effect.fx", 0, "fx_5_0", &buffer);

  if(!compileResult)
  {
    MessageBox(0, "Error compiling effect!", "Error", MB_OK);
    return false;
  }

  HRESULT d3dResult;

  d3dResult = D3DX11CreateEffectFromMemory(buffer->GetBufferPointer(), buffer->GetBufferSize(), 0, Device, &Effect);

  if(FAILED(d3dResult))
  {
    MessageBox(0, "Error creating effect shader!", "Error", MB_OK);
    if(buffer)
      buffer->Release();
    return false;
  }


  // Begin shaders
  D3D11_INPUT_ELEMENT_DESC layouts[] = 
  {
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },    
  };


  unsigned int totalLayoutElements = ARRAYSIZE(layouts);

  ID3DX11EffectTechnique* technique = Effect->GetTechniqueByName("Alpha");
  
  ID3DX11EffectPass* pass = technique->GetPassByIndex(0);

  D3DX11_PASS_SHADER_DESC passDesc;
  D3DX11_EFFECT_SHADER_DESC effectDesc;
  pass->GetVertexShaderDesc(&passDesc);
  passDesc.pShaderVariable->GetShaderDesc( passDesc.ShaderIndex, &effectDesc );


  d3dResult = Device->CreateInputLayout(layouts, totalLayoutElements, effectDesc.pBytecode, 
    effectDesc.BytecodeLength, &InputLayout);

  buffer->Release();


  if(FAILED(d3dResult))
  {
    return false;
  }  

  // end shaders
  Camera = new CameraDevice();
  Chessboard = dynamic_cast<Game::BoardMesh*>(AssetManager::Load(Chess::FigureTypeEnum::BOARD));
  return true;
}

bool CurrentContext::CompileShader(char* filePath, char *entry, char* shaderModel, ID3DBlob** buffer)
{
  DWORD shaderFlags = D3DCOMPILE_ENABLE_STRICTNESS;

#if defined(DEBUG) || defined(_DEBUG)
  shaderFlags |= D3DCOMPILE_DEBUG;
#endif

  ID3DBlob* errorBuffer = 0;
  HRESULT result = D3DX11CompileFromFile(filePath, 0, 0, entry, shaderModel, shaderFlags, 
    0, 0, buffer, &errorBuffer, 0);


  if(FAILED(result))
  {
    if(errorBuffer != 0)
    {
      OutputDebugStringA((char*) errorBuffer->GetBufferPointer());
      errorBuffer->Release();
    }
    return false;
  }

  if(errorBuffer != 0)
    errorBuffer->Release();

  return true;
}

bool CurrentContext::CreateEffect(ID3DBlob* buffer, UINT flags, ID3DX11Effect** outEffect)
{
  if(FAILED(D3DX11CreateEffectFromMemory(buffer->GetBufferPointer(), buffer->GetBufferSize(), 0, Device, outEffect)))
  {
    return false;
  }

  return true;
}